// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description:sysmonitor:fs use notification chain to monitor fd number
 * Author: hanhuihui
 * Create: 2024-4-22
 */
#include <linux/module.h>
#include <linux/sysmonitor_fd.h>

static ATOMIC_NOTIFIER_HEAD(fdstat_notifier_list);
static unsigned int fdenable;
static unsigned int fdthreshold = 80;
static unsigned int files_panic_enable;

int register_fdstat_notifier(struct notifier_block *nb)
{
	if (nb == NULL) {
		pr_err("register fdstat notification failed. nb is NULL\n");
		return -EINVAL;
	}

	if (nb->notifier_call == NULL) {
		pr_err("register fdstat notification failed. notifier_call is NULL\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_register(&fdstat_notifier_list, nb);
}
EXPORT_SYMBOL(register_fdstat_notifier);

int unregister_fdstat_notifier(struct notifier_block *nb)
{
	if (nb == NULL) {
		pr_err("unregister fdstat notification failed. nb is NULL\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_unregister(&fdstat_notifier_list, nb);
}
EXPORT_SYMBOL(unregister_fdstat_notifier);

static int fdenable_show(struct seq_file *userfile, void *v)
{
	seq_printf(userfile, "%u\n", fdenable);

	return 0;
}

static int fdenable_open(struct inode *inode, struct file *file)
{
	single_open(file, fdenable_show, NULL);

	return 0;
}

static ssize_t fdenable_write(struct file *file, const char __user *ubuf,
		size_t cnt, loff_t *ppos)
{
	int ret;
	unsigned int val;

	ret = kstrtouint_from_user(ubuf, cnt, 10, &val);
	if (ret) {
		pr_err("[fdstat] parse fdenable failed\n");
		return ret;
	}

	if (val != 0 && val != 1) {
		pr_err("[fdstat] fdenable is invalid\n");
		return -EINVAL;
	}

	fdenable = val;

	return cnt;
}

static const struct proc_ops fdenable_operations = {
	.proc_open           = fdenable_open,
	.proc_read           = seq_read,
	.proc_lseek         = seq_lseek,
	.proc_release        = single_release,
	.proc_write          = fdenable_write,
};

static int fdthreshold_show(struct seq_file *userfile, void *v)
{
	seq_printf(userfile, "%u\n", fdthreshold);
	return 0;
}

static int fdthreshold_open(struct inode *inode, struct file *file)
{
	single_open(file, fdthreshold_show, NULL);

	return 0;
}

static ssize_t fdthreshold_write(struct file *file, const char __user *ubuf,
		size_t cnt, loff_t *ppos)
{
	int ret;
	unsigned int val;

	ret = kstrtouint_from_user(ubuf, cnt, 10, &val);
	if (ret) {
		pr_err("[fdstat] parse fdthreshold failed\n");
		return ret;
	}

	if (val < 1 || val > 99) {


		pr_err("[fdstat] fdthreshold is invalid\n");
		return -EINVAL;
	}

	fdthreshold = val;

	return cnt;
}

static const struct proc_ops fdthreshold_operations = {
	.proc_open	= fdthreshold_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= single_release,
	.proc_write     = fdthreshold_write,
};

static int __init kernel_fdstat_init(void)
{
	struct proc_dir_entry *fdenable_entry;
	struct proc_dir_entry *fdthreshold_entry;

	fdenable_entry = proc_create("fdenable", 0600, NULL, &fdenable_operations);
	if (!fdenable_entry) {
		pr_err("[fdstat] create /proc/fdenable failed\n");
		return -ENOMEM;
	}

	fdthreshold_entry = proc_create("fdthreshold", 0600, NULL, &fdthreshold_operations);
	if (!fdthreshold_entry) {
		pr_err("[fdstat]: create /proc/fdthreshold failed\n");
		remove_proc_entry("fdenable", NULL);
		return -ENOMEM;
	}

	return 0;
}
module_init(kernel_fdstat_init);

void fdthreshold_call_chain(unsigned int fd, unsigned int end)
{
	struct fdstat fdstat_notifier_call;
	/*
	 * if fd is equal to fd`s threshold
	 * we notify to /proc/fdstat when fd alloc succeed.
	 */

	if (fdenable == 1
		&& (unsigned long)fd == (unsigned long)end * (unsigned long)fdthreshold / 100) {
		fdstat_notifier_call.pid = current->pid;
		fdstat_notifier_call.total_fd_num = fd;
		memcpy(fdstat_notifier_call.comm, current->comm, sizeof(fdstat_notifier_call.comm));
		(void)atomic_notifier_call_chain(&fdstat_notifier_list, 0,
							(void *)(&fdstat_notifier_call));
	}
}

void files_num_check(long nr_files, unsigned long max_files)
{
	if (files_panic_enable && nr_files >= max_files)
		panic("file-nr[%ld] is greater than or equal to file-max[%ld]\n",
			nr_files, max_files);
}

static int __init files_panic_enable_setup(char *str)
{
	unsigned long val;

	if (kstrtoul(str, 0, &val)) {
		pr_err("parse cmdline files_panic_enable failed.");
		return 0;
	}
	files_panic_enable = !!val;

	return 1;
}
__setup("files_panic_enable=", files_panic_enable_setup);
